package org.teamswift.crow.rbac.controller;

import com.google.common.base.Strings;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.teamswift.crow.rbac.annotation.PermissionAction;
import org.teamswift.crow.rbac.common.CrowDestructibleController;
import org.teamswift.crow.rbac.dto.ChangePasswordDto;
import org.teamswift.crow.rbac.dto.CrowUserDto;
import org.teamswift.crow.rbac.entity.CrowRole;
import org.teamswift.crow.rbac.entity.CrowUser;
import org.teamswift.crow.rbac.enums.CrowRbacErrors;
import org.teamswift.crow.rbac.repository.CrowRoleRepository;
import org.teamswift.crow.rbac.repository.CrowUserRepository;
import org.teamswift.crow.rbac.security.PasswordHelper;
import org.teamswift.crow.rbac.service.CrowUserService;
import org.teamswift.crow.rbac.vo.CrowUserVo;
import org.teamswift.crow.rest.common.ICrowDBService;
import org.teamswift.crow.rest.exception.BusinessException;
import org.teamswift.crow.rest.exception.CrowErrorMessage;
import org.teamswift.crow.rest.exception.impl.DataNotFoundException;
import org.teamswift.crow.rest.handler.RequestBodyResolveHandler;
import org.teamswift.crow.rest.handler.requestParams.RequestBodyResolved;
import org.teamswift.crow.rest.provider.jpa.CrowControllerJpa;
import org.teamswift.crow.rest.result.CrowResult;
import org.teamswift.crow.rest.result.ICrowListResult;
import org.teamswift.crow.rest.result.ICrowResult;
import org.teamswift.crow.rest.utils.CrowMessageUtil;
import org.teamswift.crow.rest.utils.DozerUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

@RestController
@RequestMapping("/crow/rbac/user")
@Transactional(rollbackFor = Exception.class)
public class CrowUserController extends CrowDestructibleController<Integer, CrowUser, CrowUserVo, CrowUserDto> {

    private final CrowUserRepository crowUserRepository;

    private final CrowRoleRepository crowRoleRepository;

    private final CrowUserService crowUserService;

    public CrowUserController(CrowUserRepository crowUserRepository, CrowRoleRepository crowRoleRepository, CrowUserService crowUserService) {
        this.crowUserRepository = crowUserRepository;
        this.crowRoleRepository = crowRoleRepository;
        this.crowUserService = crowUserService;
    }

    @RequestMapping(value = "/changePassword/{id}", method = RequestMethod.PUT)
    public void changePassword(@PathVariable Integer id, @RequestBody ChangePasswordDto dto) {

        CrowUser crowUser = crowUserService.findOneById(id);

        if(crowUser == null) {
            throw new DataNotFoundException(
                    CrowMessageUtil.error(
                            CrowErrorMessage.NotFoundByID
                    )
            );

        }

        if(!dto.getNewPasswordRepeat().equals(dto.getNewPassword())) {
            throw new BusinessException(
                    CrowMessageUtil.error(CrowRbacErrors.TwoPasswordNotEqual)
            );
        }

        crowUser.setPassword(dto.getNewPassword());
        PasswordHelper.encrypt(crowUser);

        crowUserRepository.save(crowUser);
    }

    @Override
    @RequestMapping
    public ICrowListResult<CrowUserVo> findAll(HttpServletRequest request) {
        RequestBodyResolved body = RequestBodyResolveHandler.handle(request, getEntityCls());
        ICrowListResult<CrowUser> raw = getCrowProvider().findAll(body);
        Collection<CrowUser> users = raw.getData();
        users = crowUserService.addRolesToList(users);

        raw.setData(users);
        return CrowResult.ofList(
                raw, getVoCls()
        );
    }

    @Override
    @RequestMapping(method = RequestMethod.POST)
    public ICrowResult<CrowUserVo> store(@Validated @RequestBody CrowUserDto dto) {
        CrowUser user = DozerUtils.map(dto, getEntityCls());

        CrowUser currentUser = (CrowUser) SecurityUtils.getSubject().getPrincipal();
        user.setOrganizationId(currentUser.getOrganizationId());
        PasswordHelper.encrypt(user);
        user = getCrowProvider().store(user);
        return CrowResult.ofSuccess(DozerUtils.map(user, getVoCls()));
    }

    @Override
    @RequestMapping(method = RequestMethod.PUT, value = "/{id}")
    public ICrowResult<CrowUserVo> update(@PathVariable Integer id, @Validated @RequestBody CrowUserDto dto) {
        ICrowDBService<Integer, CrowUser> provider = getCrowProvider();
        CrowUser toUpdate = DozerUtils.map(dto, getEntityCls());
        CrowUser user = provider.update(id, toUpdate);

        if(!Strings.isNullOrEmpty(dto.getPassword())) {
            PasswordHelper.encrypt(user);
            user = provider.store(user);
        }

        return CrowResult.ofSuccess(DozerUtils.map(user, getVoCls()));
    }

    @PermissionAction("ASSIGN_ROLE")
    @RequestMapping(method = RequestMethod.PUT, value = "/assignRole/{id}")
    public void assignRole(@PathVariable Integer id, @RequestBody List<Integer> rolesId) {
        CrowUser user = crowUserRepository.findById(id).orElseThrow(() -> {
            throw new BusinessException(
                    CrowMessageUtil.error(CrowErrorMessage.NotFoundByID)
            );
        });

        List<CrowRole> roles = crowRoleRepository.findAllById(rolesId);

        crowUserService.assignRole(user, new HashSet<>(roles));
    }

}
